/*
 * Copyright Aristool AG Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.opprua.tidy.beans;

import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;

import org.opprua.tidy.conversion.ChangerBroker;

/**
 * TODO: add Java doc
 * 
 * Prepared for first commit: 15.07.2007
 * 
 * @author Lin Li
 */
class BeanXMLStreamer {

  private Writer writer;
  private boolean tagOpen;
  private Stack<String> tags;

  private BeanXMLStreamer(Writer stream) {
    this.writer = stream;
    write("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n");
    tagOpen = false;
    tags = new Stack<String>();
  }

  /**
   */
  public static String stream(Object object) {
    StringWriter b = new StringWriter();
    BeanXMLStreamer x = new BeanXMLStreamer(b);
    if (object != null) x.collect(object, null);
    return b.toString();
  }

  /**
   */
  public static void stream(Object object, Writer b) {
    BeanXMLStreamer x = new BeanXMLStreamer(b);
    if (object != null) x.collect(object, null);
  }

  private void collect(Object object, String name) {
    if (object == null) {
      beginTag("null", name);
      endTag("null");
      return;
    }
    if (object.getClass().isArray()) {
      beginTag("a", name);
      for (int i = 0; i < Array.getLength(object); i++) {
        collect(Array.get(object, i), null);
      }
      endTag("a");
    } else if (Properties.class == object.getClass()) {
      packProperties((Properties) object, name);
    } else {
      Class<?> oclass = object.getClass();
      Map<String, BeanField> fs = BeanField.getFields(oclass);
      if (oclass == String.class) {
        beginTag("v", name);
        if (((String) object).trim().length() > 0) {
          addText((String) object, true);
        }
        endTag("v");
      } else if (ChangerBroker.getDefaultValue(oclass) != null
                  || fs.size() == 0) {
        beginTag("v", name);
        addText(ChangerBroker.convert(String.class, object), false);
        endTag("v");
      } else {
        beginTag("b", name);

        for (Iterator<String> iter = fs.keySet().iterator(); iter.hasNext();) {
          String n = iter.next();
          BeanField g = fs.get(n);
          Object d = g.get(object);
          collect(d, n);
        }
        endTag("b");
      }
    }
  }

  private void packProperties(Properties p, String name) {
    beginTag("p", name);
    for (Iterator<Object> iter = p.keySet().iterator(); iter.hasNext();) {
      String k = (String) iter.next();
      String v = p.getProperty(k);
      beginTag("p", name);
      addAttribute("k", k);
      addText(v, true);
      endTag("p");
    }
    endTag("p");
  }

  private void beginTag(String name, String aname) {
    if (tagOpen) {
      write(">");
    }
    tagOpen = true;
    tags.push(name);
    write("<").write(name);
    if (aname != null) {
      write(" n=\"").write(aname).write("\"");
    }
  }

  private void endTag(String name) {
    String t = (String) tags.pop();
    if (!name.equals(t)) {
      throw new Error("Mismatching end tag: " + name + ", was " + t);
    }
    if (tagOpen) {
      write("/>");
    } else {
      write("</").write(name).write(">");
    }
    tagOpen = false;
  }

  private void addAttribute(String name, String value) {
    if (!tagOpen) {
      throw new Error("No open tag to add attributes");
    } else if (value != null) {
      write(" ").write(name).write("=\"");
      write(value).write("\"");
    }
  }

  private void addText(String text, boolean cdata) {
    if (tagOpen) {
      write(">");
      tagOpen = false;
    }
    if (cdata) {
      write("<![CDATA[");
    }
    write(text);
    if (cdata) {
      write("]]>");
    }
  }

  private BeanXMLStreamer write(String s) {
    try {
      writer.write(s);
      return this;
    } catch (Throwable e) {
      throw new Error("Failed to write into the export writer");
    }
  }
}
